package unreleased.geppetto.cat.models;


import geppetto.cat.alignments.Alignment;
import geppetto.cat.alignments.output.AlignerOutputLatex;
import geppetto.cat.constrains.ConstrainedProjectionStats;
import geppetto.cat.corpus.BilingualCorpus;
import geppetto.cat.models.AbstractModel;
import geppetto.cat.models.AbstractSparseTranslationTable;
import geppetto.cat.models.HMM;
import geppetto.cat.models.stats.AgreeEStepStats;
import geppetto.cat.models.stats.ProjectionStats;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;




public class AgreementHMMIterative extends AbstractModel {

	HMM forward;

	HMM backward;

	double epsilon;

	int iterations;

	boolean _trained = false;

	public AgreementHMMIterative(BilingualCorpus forwardCorpus,
			BilingualCorpus backwardCorpus, double epsilon, int iterations) {
		this.epsilon = epsilon;
		this.iterations = iterations;
		this._corpus = forwardCorpus;
		forward = new HMM(forwardCorpus);
		backward = new HMM(backwardCorpus);
	}

	public AgreementHMMIterative(BilingualCorpus forwardCorpus,
			BilingualCorpus backwardCorpus, AbstractSparseTranslationTable forwardT,
			AbstractSparseTranslationTable backwardT, double epsilon, int iterations) {
		this.epsilon = epsilon;
		this.iterations = iterations;
		this._corpus = forwardCorpus;
		forward = new HMM(forwardCorpus, forwardT);
		backward = new HMM(backwardCorpus, backwardT);
	}

	AgreementHMMIterative() {

	}

	// /////////////////SAVE AND LOAD MODELS

	public void saveModel(String directory) {
		try {
			PrintStream file = new PrintStream(new FileOutputStream(directory
					+ "epsilon"));
			file.println(iterations);
			file.println(epsilon);
			forward.saveModel(directory + "forward");
			backward.saveModel(directory + "backward");
		} catch (FileNotFoundException e) {
			System.out
					.println("Model2 Agrrement Constrain: Could not save model");
			System.exit(-1);
		}
	}

	public static AgreementHMMIterative loadModel(
			BilingualCorpus forwardCorpus, BilingualCorpus backwardCorpus,
			String directory) {
		System.out.println("Forward" + forwardCorpus);
		System.out.println("Backward" + backwardCorpus);
		AgreementHMMIterative model = new AgreementHMMIterative();
		try {
			BufferedReader file = new BufferedReader(new FileReader(directory
					+ "epsilon"));
			model.iterations = Integer.parseInt(file.readLine());
			model.epsilon = Double.parseDouble(file.readLine());
			System.out.println("Reading Forward: " + directory + "forward");
			model.forward = HMM.loadModel(forwardCorpus, directory + "forward");
			model._corpus = forwardCorpus;
			System.out.println("Reading Forward: " + directory + "backward");
			model.backward = HMM.loadModel(backwardCorpus, directory
					+ "backward");
		} catch (FileNotFoundException e) {
			System.out
					.println("Model1 Agreement Constrain: Could not read model file not found "
							+ e.toString());
			System.exit(-1);
		} catch (NumberFormatException e) {
			System.out
					.println("Model1 Agreement Constrain: Could not read model number conversion exception"
							+ e.toString());
			System.exit(-1);

		} catch (IOException e) {
			System.out
					.println("Model1 Agreement Constrain: Could not read model"
							+ e.toString());
			System.exit(-1);
		}
		return model;
	}

	AgreementHMMIterative(HMM forward, HMM backward) {
		this.forward = forward;
		this.backward = backward;
		this._corpus = forward._corpus;
	}

	public AgreementHMMIterative getBackwardModel() {
		AgreementHMMIterative res = new AgreementHMMIterative(backward, forward);
		res._trained = _trained;
		return res;
	}

	public void setTrained() {
		_trained = true;
		forward.setTrained();
		backward.setTrained();
	}

	public void initializeTrain() {
		forward.initializeTrain();
		backward.initializeTrain();
	}

	public void finalizeTrain() {
		forward.finalizeTrain();
		backward.finalizeTrain();
	}

	// TODO fornow just use the forward model
	public double getNullPhrasePosterior(int phraseNumber, byte phraseSource,
			int[] foreingSentence, int[] sourceSentence, int startForeignIndex,
			int endForeignIndex) {
		return forward.getNullPhrasePosterior(phraseNumber, phraseSource,
				foreingSentence, sourceSentence, startForeignIndex,
				endForeignIndex);
	}

	// TODO fornow just use the forward model
	public double getPhrasePosterior(int phraseNumber, byte phraseSource,
			int[] foreingSentence, int[] sourceSentence, int startSourceIndex,
			int endSourceIndex, int startForeignIndex, int endForeignIndex) {
		return forward.getPhrasePosterior(phraseNumber, phraseSource,
				foreingSentence, sourceSentence, startSourceIndex,
				endSourceIndex, startForeignIndex, endForeignIndex);
	}

	public ProjectionStats projectPosteriors(double[][] probCacheF,
			double[][] posteriorsF, double[][] forwardF, double[][] backwardF,
			double[][] probCacheB, double[][] posteriorsB, double[][] forwardB,
			double[][] backwardB, int sSize, int fSize, boolean postStats) {
		ProjectionStats stats = new ProjectionStats(iterations);

		// TODO Here order of indexes is different than from posterior matrixes
		// Original caches
		// double[][] probCacheFP = common.StaticTools.copyMatrix(probCacheF,
		// fSize,sSize*2);
		// double[][] probCacheBP =common.StaticTools.copyMatrix(probCacheB,
		// sSize, fSize*2);

		double[][] originalPosteriorF = null;
		;
		double[][] originalPosteriorB = null;
		if (postStats) {
			originalPosteriorF = geppetto.cat.common.StaticTools.copyMatrix(posteriorsF,
					fSize, sSize * 2);
			originalPosteriorB = geppetto.cat.common.StaticTools.copyMatrix(posteriorsB,
					sSize, fSize * 2);
		}

		double[][] phi = new double[fSize][sSize];
		for (int grI = 0; grI < iterations; grI++) {

			double lRate = 10.0 / (10.0 + grI);
			double totalViolation = 0;
			for (int fi = 0; fi < fSize; fi++) {
				for (int si = 0; si < sSize; si++) {
					// Update phi and lambda
					double ePsi = posteriorsF[fi][si] - posteriorsB[si][fi];
					double phiUpdate = lRate * (-ePsi);
					totalViolation += Math.abs(-ePsi);
					double newlambda = Math.abs(phi[fi][si]) - lRate * epsilon;
					double newPhi = phiUpdate + phi[fi][si];
					// Project
					if (-newPhi <= newlambda && newlambda <= newPhi) {
						newPhi = (newPhi + newlambda) / 2;
						newlambda = newPhi;
					} else if (newPhi <= newlambda && newlambda <= -newPhi) {
						newPhi = (newPhi - newlambda) / 2;
						newlambda = -newPhi;
					} else {
						newPhi = 0;
						newlambda = 0;
					}
					probCacheF[fi][si] = probCacheF[fi][si] * Math.exp(newPhi);
					probCacheB[si][fi] = probCacheB[si][fi] * Math.exp(-newPhi);
					phi[fi][si] = newPhi;
				}

			}
			// Renormalizing

			forwardF = forward.makeForward(sSize, fSize, probCacheF);
			backwardF = forward.makeBackward(sSize, fSize, probCacheF);
			double likelihoodF = forward.makeLikelihood(forwardF[0],
					backwardF[0]);
			posteriorsF = forward.makePosterior(forwardF, backwardF,
					likelihoodF);

			forwardB = backward.makeForward(fSize, sSize, probCacheB);
			backwardB = backward.makeBackward(fSize, sSize, probCacheB);
			double likelihoodB = backward.makeLikelihood(forwardB[0],
					backwardB[0]);
			posteriorsB = backward.makePosterior(forwardB, backwardB,
					likelihoodB);

			if (postStats) {
				stats.addViolation(totalViolation, grI);
				// Calculate thw KL(q|p) which is what we are minimizing
				stats
						.addForwardKL(geppetto.cat.common.StaticTools.KLDistance(
								posteriorsF, originalPosteriorF, fSize,
								sSize * 2), grI);
				stats
						.addBackwardKL(geppetto.cat.common.StaticTools.KLDistance(
								posteriorsB, originalPosteriorB, sSize,
								fSize * 2), grI);
			}
		}

		return stats;
	}

	public AgreeEStepStats eStep() {
		return eStep(false);
	}

	public AgreeEStepStats eStep(boolean postStats) {
		double totalLikelihoodF = 0;
		double totalLikelihoodB = 0;
		int numZeroLikelihood = 0;
		ProjectionStats totalPStats = new ProjectionStats(iterations);
		for (int i = 0; i < forward._nSentences; i++) {
			int[] s = forward._corpus.getSourceSentence(i,
					BilingualCorpus.TRAIN_CORPUS);
			final int sSize = s.length;
			int[] f = forward._corpus.getForeignSentence(i,
					BilingualCorpus.TRAIN_CORPUS);
			final int fSize = f.length;

			// indexed by[fi][si] sSize..2*sSize corresponds to null word
			// probability
			double[][] probCacheF = forward.makeProbCache(f, s);
			double[][] forwardF = forward.makeForward(sSize, fSize, probCacheF);
			double[][] backwardF = forward.makeBackward(sSize, fSize,
					probCacheF);
			double likelihoodF = forward.makeLikelihood(forwardF[0],
					backwardF[0]);

			// indexed by[si][fi] sSize..2*sSize corresponds to null word
			// probability
			double[][] probCacheB = backward.makeProbCache(s, f);
			double[][] forwardB = backward
					.makeForward(fSize, sSize, probCacheB);
			double[][] backwardB = backward.makeBackward(fSize, sSize,
					probCacheB);
			double likelihoodB = backward.makeLikelihood(forwardB[0],
					backwardB[0]);

			assert forwardB.length == sSize;
			assert backwardB.length == sSize;

			// check for underflow
			if (likelihoodF <= 1.0e-200 || likelihoodB <= 1.0e-200) {
				numZeroLikelihood++;
				continue;
			}

			double[][] posteriorsF = forward.makePosterior(forwardF, backwardF,
					likelihoodF);
			double[][] posteriorsB = backward.makePosterior(forwardB,
					backwardB, likelihoodB);

			totalPStats.add(projectPosteriors(probCacheF, posteriorsF,
					forwardF, backwardF, probCacheB, posteriorsB, forwardB,
					backwardB, sSize, fSize, postStats));

			// TODO Check if this is working correctly
			likelihoodF = forward.makeLikelihood(forwardF[0], backwardF[0]);
			likelihoodB = backward.makeLikelihood(forwardB[0], backwardB[0]);
			totalLikelihoodF += Math.log(likelihoodF);
			totalLikelihoodB += Math.log(likelihoodB);
			forward.addToCounts(s, f, posteriorsF);
			backward.addToCounts(f, s, posteriorsB);

			forward.addToTransitions(probCacheF, forwardF, backwardF,
					likelihoodF);
			backward.addToTransitions(probCacheB, forwardB, backwardB,
					likelihoodB);
		}

		if (numZeroLikelihood > 0) {
			System.out.println("Numerical underflow happened "
					+ numZeroLikelihood + " times");
		}
		AgreeEStepStats d = new AgreeEStepStats();
		d.logLikelihoodF = totalLikelihoodF;
		d.logLikelihoodB = totalLikelihoodB;
		d.numSents = forward._corpus.getNumberOfTrainingSentences();
		d.pStats = totalPStats;
		return d;

	}

	public void mStep() {
		forward.mStep();
		backward.mStep();
	}

	public Alignment posteriorDecodingAlignment(int sentenceNumber,
			byte sentenceSource, float treshhold,boolean projectPosteriors, ConstrainedProjectionStats stats) {
		return forward.posteriorDecodingAlignment(sentenceNumber,
				sentenceSource, treshhold);
	}

	public Alignment viterbiAlignment(int sentenceNumber, byte sentenceSource, boolean projectPosteriors, ConstrainedProjectionStats stats) {
		if(projectPosteriors){
			System.out.println("Ignoring project posteriors");
		}
		return forward.viterbiAlignment(sentenceNumber, sentenceSource,  projectPosteriors);
	}

	
	public Alignment posteriorDecodingAgreementAlignment(int sentenceNumber,
			byte sentenceSource, float treshhold) {
		int[] s = forward._corpus.getSourceSentence(sentenceNumber,
				sentenceSource);
		final int sSize = s.length;
		int[] f = forward._corpus.getForeignSentence(sentenceNumber,
				sentenceSource);
		final int fSize = f.length;

		/*
		 * indexed by[fi][si] sSize..2*sSize corresponds to null word
		 * probability
		 */
		double[][] probCacheF = forward.makeProbCache(f, s);
		double[][] forwardF = forward.makeForward(sSize, fSize, probCacheF);
		double[][] backwardF = forward.makeBackward(sSize, fSize, probCacheF);
		double likelihoodF = forward.makeLikelihood(forwardF[0], backwardF[0]);

		/*
		 * indexed by[si][fi] sSize..2*sSize corresponds to null word
		 * probability
		 */
		double[][] probCacheB = backward.makeProbCache(s, f);
		double[][] forwardB = backward.makeForward(fSize, sSize, probCacheB);
		double[][] backwardB = backward.makeBackward(fSize, sSize, probCacheB);
		double likelihoodB = backward.makeLikelihood(forwardB[0], backwardB[0]);

		assert forwardB.length == sSize;
		assert backwardB.length == sSize;

		assert likelihoodB > 0 && likelihoodF > 0;

		/*
		 * indexed by[fi][si] sSize..2*sSize corresponds to null word
		 * probability
		 */
		double[][] posteriorsF = forward.makePosterior(forwardF, backwardF,
				likelihoodF);
		/*
		 * indexed by[si][fi] fSize..2*fSize corresponds to null word
		 * probability
		 */
		double[][] posteriorsB = backward.makePosterior(forwardB, backwardB,
				likelihoodB);

		for (int fi = 0; fi < fSize; fi++) {
			for (int si = 0; si < sSize; si++) {
				posteriorsF[fi][si] = posteriorsF[fi][si] * posteriorsB[si][fi];
				posteriorsB[si][fi] = posteriorsF[fi][si];

			}
		}

		Alignment a = new Alignment(sentenceNumber, sentenceSource, sSize,
				fSize);

		for (int fi = 0; fi < fSize; fi++) {
			for (int si = 0; si < sSize; si++) {
				if (posteriorsF[fi][si] > treshhold)
					a.add(si, fi);
			}
		}
		return a;
	}

	public String getName() {
		return "New Agremement Constrain HMM";
	}

	

	


	// Prints a specific alignment before and after projection
	// These method should be abstracted
	public void printPosteriorAlignment(Alignment fa, Alignment ba,
			int sentenceNumber, byte sentenceSource,
			BilingualCorpus forwardCorpus, BilingualCorpus backwardCorpus)
			throws UnsupportedEncodingException {

		int[] s = forward._corpus.getSourceSentence(sentenceNumber,
				sentenceSource);
		final int sSize = s.length;
		int[] f = forward._corpus.getForeignSentence(sentenceNumber,
				sentenceSource);
		final int fSize = f.length;

		// indexed by[fi][si] sSize..2*sSize corresponds to null word
		// probability
		double[][] probCacheF = forward.makeProbCache(f, s);
		double[][] forwardF = forward.makeForward(sSize, fSize, probCacheF);
		double[][] backwardF = forward.makeBackward(sSize, fSize, probCacheF);
		double likelihoodF = forward.makeLikelihood(forwardF[0], backwardF[0]);

		// indexed by[si][fi] sSize..2*sSize corresponds to null word
		// probability
		double[][] probCacheB = backward.makeProbCache(s, f);
		double[][] forwardB = backward.makeForward(fSize, sSize, probCacheB);
		double[][] backwardB = backward.makeBackward(fSize, sSize, probCacheB);
		double likelihoodB = backward.makeLikelihood(forwardB[0], backwardB[0]);

		// Original caches
		double[][] probCacheFP = probCacheF.clone();
		double[][] probCacheBP = probCacheB.clone();

		double[][] posteriorsF = forward.makePosterior(forwardF, backwardF,
				likelihoodF);
		double[][] posteriorsB = backward.makePosterior(forwardB, backwardB,
				likelihoodB);

		// Initial Posteriors add them to alignment and print
		for (int fi = 0; fi < f.length; fi++) {
			for (int si = 0; si < s.length; si++) {
				fa.addPosterior(si, fi, posteriorsF[fi][si]);
			}
		}

		for (int fi = 0; fi < f.length; fi++) {
			for (int si = 0; si < s.length; si++) {
				ba.addPosterior(fi, si, posteriorsB[si][fi]);
			}
		}

		System.out.println("Sentence nr" + sentenceNumber);
		System.out.println("Before Projection" + sentenceNumber);
		AlignerOutputLatex.outputWithGoldPostLatex(fa, forwardCorpus.getGold()
				.get(sentenceNumber), forwardCorpus, System.out, "");
		System.out.println();
		System.out.println();
		AlignerOutputLatex.outputWithGoldPostLatex(ba, backwardCorpus.getGold()
				.get(sentenceNumber), backwardCorpus, System.out, "");
		System.out.println();
		System.out.println();
		double[][] phi = new double[fSize][sSize];
		for (int grI = 0; grI < iterations; grI++) {

			double lRate = 10.0 / (10.0 + grI);
			double totalViolation = 0;
			for (int fi = 0; fi < fSize; fi++) {
				for (int si = 0; si < sSize; si++) {
					// Update phi and lambda
					double ePsi = posteriorsF[fi][si] - posteriorsB[si][fi];
					double phiUpdate = lRate * (-ePsi);
					totalViolation += Math.abs(-ePsi);
					double newlambda = Math.abs(phi[fi][si]) - lRate * epsilon;
					double newPhi = phiUpdate + phi[fi][si];
					// Project
					if (-newPhi <= newlambda && newlambda <= newPhi) {
						newPhi = (newPhi + newlambda) / 2;
						newlambda = newPhi;
					} else if (newPhi <= newlambda && newlambda <= -newPhi) {
						newPhi = (newPhi - newlambda) / 2;
						newlambda = -newPhi;
					} else {
						newPhi = 0;
						newlambda = 0;
					}
					probCacheF[fi][si] = probCacheFP[fi][si] * Math.exp(newPhi);
					probCacheB[si][fi] = probCacheBP[si][fi]
							* Math.exp(-newPhi);
					phi[fi][si] = newPhi;
				}
			}
			// Renormalizing
			forwardF = forward.makeForward(sSize, fSize, probCacheF);
			backwardF = forward.makeBackward(sSize, fSize, probCacheF);
			likelihoodF = forward.makeLikelihood(forwardF[0], backwardF[0]);
			posteriorsF = forward.makePosterior(forwardF, backwardF,
					likelihoodF);

			forwardB = backward.makeForward(fSize, sSize, probCacheB);
			backwardB = backward.makeBackward(fSize, sSize, probCacheB);
			likelihoodB = backward.makeLikelihood(forwardB[0], backwardB[0]);
			posteriorsB = backward.makePosterior(forwardB, backwardB,
					likelihoodB);
		}

		for (int fi = 0; fi < f.length; fi++) {
			for (int si = 0; si < s.length; si++) {
				fa.addPosterior(si, fi, posteriorsF[fi][si]);
			}
		}

		for (int fi = 0; fi < f.length; fi++) {
			for (int si = 0; si < s.length; si++) {
				ba.addPosterior(fi, si, posteriorsB[si][fi]);
			}
		}
		AlignerOutputLatex.outputWithGoldPostLatex(fa, forwardCorpus.getGold()
				.get(sentenceNumber), forwardCorpus, System.out, "");
		System.out.println();
		System.out.println();
		AlignerOutputLatex.outputWithGoldPostLatex(ba, backwardCorpus.getGold()
				.get(sentenceNumber), backwardCorpus, System.out, "");
		System.out.println();
		System.out.println();
		System.out.println("Ended sentence " + sentenceNumber);
	}

}

/*
 * public AgreeEStepStats eStep(){ double totalLikelihood = 0; int
 * numZeroLikelihood = 0; for (int i=0; i < forward._nSentences; i++){ int[] s =
 * forward._corpus.getSourceSentence(i, BilingualCorpus.TRAIN_CORPUS); final int
 * sSize = s.length; int[] f = forward._corpus.getForeignSentence(i,
 * BilingualCorpus.TRAIN_CORPUS); final int fSize = f.length;
 *  // indexed by[fi][si] sSize..2*sSize corresponds to null word probability
 * double[][] probCacheF = forward.makeProbCache(f,s); double[][] forwardF =
 * forward.makeForward(sSize, fSize, probCacheF); double[][] backwardF =
 * forward.makeBackward(sSize, fSize, probCacheF); double likelihoodF =
 * forward.makeLikelihood(forwardF[0], backwardF[0]);
 * 
 * 
 *  // indexed by[si][fi] sSize..2*sSize corresponds to null word probability
 * double[][] probCacheB = backward.makeProbCache(s,f); double[][] forwardB =
 * backward.makeForward(fSize, sSize, probCacheB); double[][] backwardB =
 * backward.makeBackward(fSize, sSize, probCacheB); double likelihoodB =
 * backward.makeLikelihood(forwardB[0], backwardB[0]);
 * 
 * assert forwardB.length == sSize; assert backwardB.length == sSize;
 *  // check for underflow if (likelihoodF <= 1.0e-200 || likelihoodB <=
 * 1.0e-200){ numZeroLikelihood++; continue; }
 * 
 * //Original caches double[][] probCacheFP = probCacheF.clone(); double[][]
 * probCacheBP = probCacheB.clone();
 * 
 * 
 * 
 * double[][] phi = new double[fSize][sSize];
 * 
 * double[][] posteriorsF = forward.makePosterior(forwardF, backwardF,
 * likelihoodF); double[][] posteriorsB = backward.makePosterior(forwardB,
 * backwardB, likelihoodB);
 * 
 * double totalViolationOld = Double.POSITIVE_INFINITY;
 * 
 * 
 * for (int grI=0; grI<iterations; grI++){
 * 
 * double lRate = 10.0/(10.0+grI); double totalViolation = 0; for (int fi=0; fi <
 * fSize; fi++){ for (int si=0; si < sSize; si++){ //Update phi and lambda
 * double ePsi = posteriorsF[fi][si] -posteriorsB[si][fi]; double phiUpdate =
 * lRate*(-ePsi); totalViolation += Math.abs(-ePsi); double newlambda =
 * Math.abs(phi[fi][si]) - lRate*epsilon; double newPhi = phiUpdate +
 * phi[fi][si]; //Project if(-newPhi <= newlambda && newlambda <= newPhi){
 * newPhi = (newPhi+newlambda)/2; newlambda = newPhi; }else if(newPhi <=
 * newlambda && newlambda <= -newPhi){ newPhi = (newPhi-newlambda)/2; newlambda =
 * -newPhi; }else{ newPhi = 0; newlambda = 0; } probCacheF[fi][si] =
 * probCacheFP[fi][si] * Math.exp(newPhi); probCacheB[si][fi] =
 * probCacheBP[si][fi] * Math.exp(-newPhi); phi[fi][si] = newPhi; }
 * 
 *  } totalViolationOld = totalViolation;
 * 
 * //Renormalizing
 * 
 * forwardF = forward.makeForward(sSize, fSize, probCacheF); backwardF =
 * forward.makeBackward(sSize, fSize, probCacheF); likelihoodF =
 * forward.makeLikelihood(forwardF[0], backwardF[0]); posteriorsF =
 * forward.makePosterior(forwardF, backwardF, likelihoodF);
 * 
 * forwardB = backward.makeForward(fSize, sSize, probCacheB); backwardB =
 * backward.makeBackward(fSize, sSize, probCacheB); likelihoodB =
 * backward.makeLikelihood(forwardB[0], backwardB[0]); posteriorsB =
 * backward.makePosterior(forwardB, backwardB, likelihoodB);
 *  } totalLikelihood += Math.log(likelihoodF);
 * 
 * forward.addToCounts( s, f, posteriorsF); backward.addToCounts(f, s,
 * posteriorsB);
 * 
 * forward.addToTransitions(probCacheF, forwardF, backwardF, likelihoodF);
 * backward.addToTransitions(probCacheB, forwardB, backwardB, likelihoodB); }
 * 
 * if (numZeroLikelihood > 0){ System.out.println("Numerical underflow happened
 * "+numZeroLikelihood+" times"); } AgreeEStepStats d = new AgreeEStepStats();
 * d.logLikelihood = totalLikelihood; d.numSents =
 * forward._corpus.getNumberOfTrainingSentences(); return d;
 *  }
 */
